#include <stdio.h>      /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), connect(), sendto(), and recvfrom() */
#include <arpa/inet.h>  /* for sockaddr_in and inet_addr() */
#include <netinet/in.h> /* gethostname */
#include <netdb.h>      /* for get hostbyname */
#include <stdlib.h>     /* for atoi() and exit() */
#include <string.h>     /* for memset() */
#include <unistd.h>     /* for close() */
#include <sys/file.h>   /* for file operations */
#include <fcntl.h>      /* for fcntl */
#include "str.h"	/* header file which declares the request struct */

#define ECHOMAX 255     /* Longest string to echo */

void DieWithError(const char *errorMessage) /* External error handling function */
{
    perror(errorMessage);
    exit(1);
}

int main(int argc, char *argv[])
{
    int sock;                        /* Socket descriptor */
    struct sockaddr_in echoServAddr; /* Echo server address */
    struct sockaddr_in fromAddr;     /* Source address of echo */
    struct in_addr **addr_list;      /* Process IP list*/
    unsigned short echoServPort;     /* Echo server port */
    unsigned int fromSize;           /* In-out of address size for recvfrom() */
    char *servIP;                    /* IP address of server */
    char *echoString;                /* String to send to echo server */
    char echoBuffer[ECHOMAX+1];      /* Buffer for receiving echoed string */
    int echoStringLen;               /* Length of string to echo */
    int respStringLen;               /* Length of received response */
    int reqs = 0;   		     /* REQUEST INTEGER COUNTER     */
    struct request arequest;		
    int timedout = 0;		     /* timeout flag */
    int i;			     /* simple counter */
    int loks;			     /* flock-lock variable */	
    int loks2;			     /* flock-unlock variable */
    FILE *Inc;			     /* File pointer to inc.txt */
    int temps;			     /* temporary variable */
    char *hostip;		     /* host ip variable */
   
    if (argc != 4)   /* Test for correct number of arguments */
    {
        fprintf(stderr,"Usage: %s <Server IP> <Port num> <Client num>\n", argv[0]);
        exit(1);
    }

/**************************************************************************
The following section uses the gethostname function to find the hostname of
the processor. This enables us to then use the gethostbyname function to 
find the associated IP, which is set as the client_ip in the request struct.
**************************************************************************/

    /* Find the hostname of the processor with gethostname*/
    char hostname[128];
    gethostname(hostname, sizeof hostname);
    printf("Hostname: %s\n", hostname);
    
    /* Find the IP associated with the hostname using gethostbyname */
    struct hostent *clip = gethostbyname(hostname);
  
    if(clip == NULL)
    {
	printf("gethostbyname failed\n");
   	   exit(1);
    }
    else  
    {
	hostip = inet_ntoa( *(struct in_addr*)( clip -> h_addr_list[0])); // host IPs are listed in the struct set by gethostbyname 
	   printf("The IP associated with %s is: %s \n", hostname, hostip);  	  
    }

    	printf("\n");  

    // initialize struct variables
    servIP = argv[1]; 
    arequest.req = 0;
    arequest.client = atoi(argv[3]);

    // set the client ip
    for (i=0; i<16; i++)
    {
       arequest.client_ip[i] = hostip[i];
    }

		
/**************************************************************************
The following section checks for an incarnation file and creates one if 
necessary. The incarnation number is read from the file if it exists but if 
not, then the incarnation number is set to 0 and the struct attribute 'inc'
is also set to 0. Locks are implemented using flock to ensure that no other 
processes write to the file.
**************************************************************************/   

    // Check if incarnation file exists and make one if not
    Inc = fopen("inc.txt" , "r");
               
     if((Inc == NULL)) // No incarnation file exists
     {	
	printf("No Incarnation File Found. Creating one\n");		
 	Inc = fopen("inc.txt", "w");
 	loks = flock(fileno(Inc), LOCK_EX);

 	   // if file lock is obtainable
	   if(loks == 0)
	   {
              fprintf(Inc, "0\n");
              arequest.inc = 0; //set incarnation number
	   }                 

           // if file is already locked, sleep and try again
	   else
	   {   
              while (loks!=0) //loop conintuously until lock is available
              {  
		sleep(1);
                loks = flock(fileno(Inc), LOCK_EX);
                printf("Trying to lock\n");
              }				
	
	        fscanf(Inc, "%d", &arequest.inc);
           }

                loks2 = flock(fileno(Inc), LOCK_UN);
                fclose(Inc);
       }
		
    // incarnation file exists, try to lock and read
    else
    {
       loks = flock(fileno(Inc), LOCK_EX);

	if(loks==0)
	{
	     while(!feof(Inc))
	     {			
	         fscanf(Inc, "%d", &temps);
             }

	     arequest.inc = temps;  // intialize incarnation number
             loks2 = flock(fileno(Inc), LOCK_UN);
	      fclose(Inc);
	}
			
	else
	{
	    while(loks!=0)  // loop until lock becomes available
	    {
		sleep(1);
	        loks = flock(fileno(Inc), LOCK_EX);
	        printf("Trying to lock\n");
	    }
			     
	    while(!feof(Inc))
	    {
		 fscanf(Inc, "%d", &temps);
	    }
			    
            arequest.inc = temps;
            loks2 = flock(fileno(Inc), LOCK_UN);
	    fclose(Inc);
	}
     }		
			
/**************************************************************************
The following section consists of the general setup for the UDP sockets.
Using socket sock, UPD is set as the protocal to use for sending the 
datagram. The server address structure is then constructed using the 
declared echoServAddr sockaddr_in structure.
**************************************************************************/

        echoServPort = atoi(argv[2]); /* MANUALLY PUTTING IN PORT NUMBER */
  
        /* Create a datagram/UDP socket */
    	if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");
    
	// set sockets to non-blocking
	int flags = fcntl(sock, F_GETFL);
    	flags |= O_NONBLOCK;
   	fcntl(sock, F_SETFL, flags);

    	/* Construct the server address structure */
   	memset(&echoServAddr, 0, sizeof(echoServAddr));    /* Zero out structure */
    	echoServAddr.sin_family = AF_INET;                 /* Internet addr family */
    	echoServAddr.sin_addr.s_addr = inet_addr(servIP);  /* Server IP address */
    	echoServAddr.sin_port   = htons(echoServPort);     /* Server port */

/**************************************************************************
This section is the "meat" of the client. This is where the sending of the
data is sent and recieved from the server. Requests are sent util all 20
are answered by the server. A timeout of 2 seconds is allowed for a response
until the request is resent.
**************************************************************************/  

    	while (arequest.req < 20) // 20 total requests starting at 0
    	{
	    /*****************************************************************
	      This is a simple random number generator for the "failure cases.
	      To fail with a probability of .5, the generator chooses 1 or 2 
  	      and the responses are judged accordingly. 1 indicates a failure
	      which increases the incarnation number in the inc.txt file. 
	      Flock is used here to lock the files.
	    *****************************************************************/ 
	
	    srand (time(NULL));
            int r = rand() % (2-1+1)+1;
      
             if( r == 1) // client "failed". Increase incarnation number from inc.txt
 	     {
		printf("Client \"Failed\". Increasing incarnation\n");
		  Inc = fopen("inc.txt", "r");
                    fscanf(Inc, "%d", &arequest.inc); // get the incarnation number from the file
		     fclose(Inc);
                    arequest.inc = arequest.inc+1; // increment the incanation 
		  Inc = fopen("inc.txt", "w");
        	loks = flock(fileno(Inc), LOCK_EX);  // get a lock to write inc.txt
        
        	if(loks == 0)
	   	    fprintf(Inc, "%d\n", arequest.inc);
        	else
		{
	       	    while (loks !=0)
		        {
		  	     loks = flock(fileno(Inc), LOCK_EX);
                  	     printf("Trying to lock\n");
                	}
           
		    fprintf(Inc, "%d\n", arequest.inc);
            	}

         	loks2 = flock(fileno(Inc), LOCK_UN);
          	fclose(Inc);
	    }

           if(timedout == 0) // Timeout flag to figure out when to resend
           { 
               /************************************************************
		This is a simple random number generator to choose one 
		letter from the alphabet to send. The generator relies on 
		ASCII conversions to convert the integers to their char
		equivalent.
		***********************************************************/
	       srand ( time(NULL));
               int r = rand() % (122-97+1)+97;
               char c = (char)r;
               arequest.c = c;  
               sleep(1);
           }

	/****************************************************************
	 This subsection sends the data to the server in the form of a 
	 pointer to the request struct. This includes the client ip,
	 request number, incarnation number, and the random character
	****************************************************************/

	 printf("Sending incarnation:%d | request:%d | client:%s \n", arequest.inc, arequest.req, arequest.client_ip);
  	  /* Send the string to the server */
   	 if (sendto(sock, &arequest, sizeof (arequest), 0, (struct sockaddr *)
        	       &echoServAddr, sizeof(echoServAddr)) != sizeof(arequest))
      	  DieWithError("sendto() sent a different number of bytes than expected");  

    	fromSize = sizeof(fromAddr);

        sleep(2); // timeout of 2 seconds to wait for response
        
        /**************************************************************
	If no response is recieved after the 2 second timeout, then the
	request is resent by sending the program counter back up to the
	top of the loop using the continue command
	**************************************************************/

	if ((respStringLen = recvfrom(sock,echoBuffer, ECHOMAX, 0, 
         (struct sockaddr *) &fromAddr, &fromSize)) <0)
         {
             printf("Response timed out. Resending\n\n");
             timedout = 1;
	     continue;
	 }
        
	else
        {
	     if (echoServAddr.sin_addr.s_addr != fromAddr.sin_addr.s_addr)
             {
                 fprintf(stderr,"Error: received a packet from unknown source.\n");
                 exit(1);
             }

	    timedout = 0;
	    arequest.req = arequest.req +1;
	    printf("Received: %s from server\n\n", echoBuffer);
	    echoBuffer[respStringLen] = '\0';
	}
     }
      
       close(sock);
       exit(0);
}
